package com.xiaocai.glsurface;

import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.Surface;

import com.xiaocai.glsurface.utils.TextResourceReader;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MyGLSurfaceView extends GLSurfaceView implements GLSurfaceView.Renderer {

    private static final String TAG = MyGLSurfaceView.class.getSimpleName();

    private SurfaceStateCallback surfaceStateCallback;

    private SurfaceTexture surfaceTexture;

    private int program;

    private int aPosition;

    private int aCoordinate;

    private FloatBuffer vertexBuffer;

    private FloatBuffer textureBuffer;

    private int u_ProjMatrix;

    private float[] project = new float[16];

    private float[] model = new float[16];

    private int videoWidth, videoHeight;

    private int width, height;

    private DisplayMode displayMode;

    private int u_ModelMatrix;

    private boolean mirrorX, mirrorY;

    private ColorFilter colorFilter = ColorFilter.NONE;

    private int uColorFilter;

    private final float[] color = new float[4];

    private int aColor;

    private int uScreenSplit;

    private ScreenSplit screenSplit = ScreenSplit.NONE;

    private Surface surface;

    public MyGLSurfaceView(Context context) {
        this(context, null);
    }

    public MyGLSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);

        setEGLContextClientVersion(2);
        setRenderer(this);
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

        float[] vertex = {
                -1, -1,
                 1,  1,
                -1,  1,
                 1,  1,
                -1, -1,
                 1, -1,
        };
        vertexBuffer = ByteBuffer.allocateDirect(vertex.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(vertex);
        vertexBuffer.position(0);

        float[] coordinate = {
                 0,  1,
                 1,  0,
                 0,  0,
                 1,  0,
                 0,  1,
                 1,  1
        };
        textureBuffer = ByteBuffer.allocateDirect(coordinate.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(coordinate);
        textureBuffer.position(0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES20.glClearColor(0, 0, 0, 1);

        program = ShaderHelper.buildProgram(TextResourceReader.readTextFileFromResource(getContext(), R.raw.vertex_shader),
                TextResourceReader.readTextFileFromResource(getContext(), R.raw.fragment_shader));

        aPosition = GLES20.glGetAttribLocation(program, "aPosition");
        aCoordinate = GLES20.glGetAttribLocation(program, "aCoordinate");
        u_ProjMatrix = GLES20.glGetUniformLocation(program, "u_ProjMatrix");
        u_ModelMatrix = GLES20.glGetUniformLocation(program, "u_ModelMatrix");
        uColorFilter = GLES20.glGetUniformLocation(program, "uColorFilter");
        aColor = GLES20.glGetAttribLocation(program, "aColor");
        uScreenSplit = GLES20.glGetUniformLocation(program, "uScreenSplit");

        createSurface();
        if (surfaceStateCallback != null) {
            surfaceStateCallback.create(surface);
        }
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        this.width = width;
        this.height = height;
        GLES20.glViewport(0, 0, width, height);

        changeDisplay();
        displayMirror();
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        GLES20.glUseProgram(program);

        GLES20.glVertexAttribPointer(aPosition, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer);
        GLES20.glEnableVertexAttribArray(aPosition);

        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glVertexAttribPointer(aCoordinate, 2, GLES20.GL_FLOAT, false, 0, textureBuffer);
        GLES20.glEnableVertexAttribArray(aCoordinate);

        GLES20.glUniformMatrix4fv(u_ProjMatrix, 1, false, project, 0);
        GLES20.glUniformMatrix4fv(u_ModelMatrix, 1, false, model, 0);

        GLES20.glUniform1i(uScreenSplit, screenSplit.value);
        GLES20.glUniform1i(uColorFilter, colorFilter.value);
        GLES20.glVertexAttrib4fv(aColor, color, 0);

        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 6);
        surfaceTexture.updateTexImage();

        GLES20.glDisableVertexAttribArray(aPosition);
        GLES20.glDisableVertexAttribArray(aCoordinate);
    }

    public void setSurfaceStateCallback(SurfaceStateCallback surfaceStateCallback) {
        this.surfaceStateCallback = surfaceStateCallback;
    }

    private void createSurface() {
        int texture = TextureHelper.loadTexture();
        surfaceTexture = new SurfaceTexture(texture);
        surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
            @Override
            public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                requestRender();
            }
        });
        surface = new Surface(surfaceTexture);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

    public void setVideoSize(int width, int height) {
        videoWidth = width;
        videoHeight = height;
    }

    /**
     * 视频显示的比例
     * @param displayMode 比例模式
     */

    public void setDisplayMode(DisplayMode displayMode) {
       this.displayMode = displayMode;
       changeDisplay();
    }

    private void changeDisplay() {
        Matrix.setIdentityM(project, 0);

        if (videoHeight == 0 || videoWidth == 0 || width == 0 || height == 0) {
            return;
        }

        float ratioWidth = width / (float) videoWidth;
        float ratioHeight = height / (float) videoHeight;

        if (displayMode == DisplayMode.RATIO) {
            if (ratioWidth > ratioHeight) {
                float r = 1 / ratioHeight * ratioWidth;
                Matrix.orthoM(project, 0, -r, r, -1, 1, 1, -1);
            } else {
                float r = 1 / ratioWidth * ratioHeight;
                Matrix.orthoM(project, 0, -1, 1, -r, r, 1, -1);
            }
        } else if (displayMode == DisplayMode.ORIGINAL) {
            Matrix.orthoM(project, 0, -ratioWidth, ratioWidth, -ratioHeight, ratioHeight, 1, -1);
        } else if (displayMode == DisplayMode.FULL_RATIO) {
            if (ratioWidth > ratioHeight) {
                float r = 1 / ratioWidth * ratioHeight;
                Matrix.orthoM(project, 0, -1, 1, -r, r, 1, -1);
            } else {
                float r = 1 / ratioHeight * ratioWidth;
                Matrix.orthoM(project, 0, -r, r, -1, 1, 1, -1);
            }
        }
    }

    /**
     * 视频镜像
     * @param x 左右镜像
     * @param y 上下镜像
     */

    public void setMirror(boolean x, boolean y) {
        mirrorX = x;
        mirrorY = y;

        displayMirror();
    }

    private void displayMirror() {
        Matrix.setIdentityM(model, 0);
        if (mirrorX) {
            Matrix.rotateM(model, 0, 180, 0, 1, 0);
        }
        if (mirrorY) {
            Matrix.rotateM(model, 0, 180, 1, 0, 0);
        }
    }

    public void setColorFilter(ColorFilter colorFilter) {
        this.colorFilter = colorFilter;
    }

    /**
     * 改变图像的argb值
     *
     * @param src argb -1 ~ 1
     */

    public void setColor(float[] src) {
        System.arraycopy(src, 0, this.color, 0, this.color.length);
    }

    /**
     * 使用分屏播放
     *
     * @param screenSplit 分屏参数
     */

    public void setScreenSplit(ScreenSplit screenSplit) {
        this.screenSplit = screenSplit;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

        if (surfaceStateCallback != null) {
            surfaceStateCallback.destroy();
        }

        if (surface != null) {
            surface.release();
            surface = null;
        }
        if (surfaceTexture != null) {
            surfaceTexture.release();
            surfaceTexture = null;
        }
    }
}
